package com.example.redis.tool;


import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisCommands;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author:xuecx
 * @descript:<p>利用redis实现分布式锁</p>
 * @date:2018/10/12
 */
public class RedisLock1 {
    /**
     * 加锁
     *
     * @param redisTemplate redis连接
     * @param lockKey       锁名称
     * @param retry         从试次数
     * @param requestId     请求id
     * @param expire        过期时间 单位s
     * @return
     */
    public static boolean lock1(RedisTemplate redisTemplate, String lockKey, Integer retry, String requestId, int expire) {
        for (int i = 0; i < retry; i++) {
            //如果键不存在则新增,存在则不改变已经有的值
            Boolean hasLock = redisTemplate.opsForValue().setIfAbsent(lockKey, requestId);
            if (hasLock) {
                //设置过期时间
                redisTemplate.expire(lockKey, expire, TimeUnit.MILLISECONDS);
                return true;
            }
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    /**
     * 加锁
     *
     * @param redisTemplate redis连接
     * @param lockKey       锁名称
     * @param retry         从试次数
     * @param requestId     请求id
     * @param expire        过期时间 单位s
     * @return
     */
    public static boolean lock(RedisTemplate redisTemplate, String lockKey, Integer retry, String requestId, int expire) {
        int i = 0;
        while (true) {
            try {
                RedisCallback<String> stringRedisCallback = new RedisCallback() {
                    @Override
                    public Object doInRedis(RedisConnection redisConnection) {
                        JedisCommands nativeConnection = (JedisCommands) redisConnection.getNativeConnection();
                        return nativeConnection.set(lockKey, requestId, "NX", "PX", expire);
                    }
                };
                String execute = (String) redisTemplate.execute(stringRedisCallback);
                return !execute.isEmpty();
            } catch (Exception e) {

            }
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            i++;
            if (i > retry) {
                return false;
            }
        }
    }

    /**
     * 释放锁,支持集群和单机
     *
     * @param redisTemplate
     * @param lockKey       锁名称
     * @param requestId     请求id
     * @return
     */
    public static synchronized boolean unLock(RedisTemplate redisTemplate, String lockKey, String requestId) {
        String UNLOCK_LUA = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        List<String> lockKeys = Collections.singletonList(lockKey);
        List<String> requestIds = Collections.singletonList(requestId);
        RedisCallback<Long> callback = (RedisConnection connection) -> {
            Object nativeConnection = connection.getNativeConnection();
            // 集群模式和单机模式虽然执行脚本的方法一样，但是没有共同的接口，所以只能分开执行
            // 集群模式
            if (nativeConnection instanceof JedisCluster) {
                return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, lockKeys, requestIds);
            }
            // 单机模式
            else if (nativeConnection instanceof Jedis) {
                return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, lockKeys, requestIds);
            }
            return 0L;
        };
        Long result = (Long) redisTemplate.execute(callback);
        return result != null && result > 0;
    }
}
